/* CC CANCPC card handling
 * (c) 2000-2020 oe@emotas.de 
 *
 * currently tested for the CANPCI-CO
 *
 * This still is old-style handling
 * walking through the list of PCI devices manually
 * This has to be replaced by pci_register_driver()
 * and the pci_probe function()
 *
 * The PCI board has two I/O register areas
 * area 2 - the SJA100 CAN controller
 * area 1: some registers controlling the on board LEDs
 *  and a hardware reset.
 * write a 0 to second_address_space_base_address+0x54
 * you will turn off the LEDs.
 * If you write a 0x16 to the same address you will see a green LED
 * and if you write an 0x32 you will see a red LED.
 *
 * If you set bit 30 (decimal) in area1_base_address+0x50
 * will enable the reset to the CAN controller.
 * Reset the bit to take the SJA1000 out of reset.
 * Perform a read-modify-write such that you don't affect any other bits
 * in this register.
 * lspci
18:07.0 Network controller [0280]: Contemporary Controls Device [1571:c001] (rev 01)
        Subsystem: Contemporary Controls Device [1571:c001]
        Control: I/O+ Mem+ BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B- DisINTx-
        Status: Cap+ 66MHz- UDF- FastB2B+ ParErr- DEVSEL=medium >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
        Interrupt: pin A routed to IRQ 21
        Region 0: Memory at d0100400 (32-bit, non-prefetchable) [size=128]
        Region 1: I/O ports at 5480 [size=128]
        Region 2: I/O ports at 5400 [size=128]
        Capabilities: [40] Power Management version 1
                Flags: PMEClk- DSI- D1- D2- AuxCurrent=0mA PME(D0+,D1-,D2-,D3hot+,D3cold-)
                Status: D0 NoSoftRst- PME-Enable- DSel=0 DScale=0 PME-
        Capabilities: [48] CompactPCI hot-swap <?>
        Capabilities: [4c] Vital Product Data
                Not readable
 */

/* use it for pr_info() and consorts */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include "defs.h"
#include <linux/pci.h>

#ifdef CAN4LINUX_PCI
# ifndef CONFIG_PCI
#   error "trying to compile a PCI driver for a kernel without CONFIG_PCI"
# endif
#endif


/* hold base address of the I/O are with led control registers */
static unsigned long ledaddr[MAX_CHANNELS];
int pcimod_scan(void);

/* special handling of pci stuff of the Contemporary Controls CANPCI */
/* ================================================================= */
static char *pcican_devnames[] = {
	"PCICAN/CANopen",
	"PCICAN/DeviceNet"
};

/* used for storing the global pci register address */
/* one element more than needed for marking the end */
struct	pci_dev *can_pcidev[MAX_CHANNELS + 1] = { NULL };


/* Called from __init,  once when driver is loaded
   set up physical addresses, irq number
   and initialize clock source for the CAN module

   Take care it will be called only once
   because it is called for every CAN channel out of MAX_CHANNELS
*/
int init_board_hw(int n)
{
static int already_called;
int ret;
int minor = -1;

	DBGIN();
	ret = 0;
	if (!already_called && virtual == 0) {
		/* make some sysctl entries read only
		 * IRQ number
		 * Base address
		 * and access mode
		 * are fixed and provided by the PCI BIOS
		 */
		can_sysctl_table[CAN_SYSCTL_IRQ - 1].mode = 0444;
		can_sysctl_table[CAN_SYSCTL_BASE - 1].mode = 0444;
		/* pr_info(KERN_INFO "CAN pci test loaded\n"); */
		/* proc_dbgmask = 0; */
		if (pcimod_scan()) {
			pr_err("no valid PCI CAN found\n");
			ret = -EIO;
		} else
		    pr_info("  pci scan success\n");
		already_called = 1;
	}
	DBGOUT();
	return ret;
}


/* we have acquired two IO regions on the board, release it */
void exit_board_hw(void)
{
int minor = -1;
    DBGIN();

    /* release region is done at close() 
	pci_release_region(can_pcidev[0], 1);
	pci_release_region(can_pcidev[0], 2);
    */
    DBGOUT();
}

int can_requestirq(int minor, int irq, irqreturn_t (*handler)(int, void *))
{
int err = 0;

    DBGIN();
    /*

    int request_irq(unsigned int irq,			// interrupt number
              void (*handler)(int, void *, struct pt_regs *), // pointer to ISR
		              irq, dev_id, registers on stack
              unsigned long irqflags, const char *devname,
              void *dev_id);

       dev_id - The device ID of this handler (see below).
       This parameter is usually set to NULL,
       but should be non-null if you wish to do  IRQ  sharing.
       This  doesn't  matter when hooking the
       interrupt, but is required so  that,  when  free_irq()  is
       called,  the  correct driver is unhooked.  Since this is a
       void *, it can point to anything (such  as  a  device-
       specific  structure,  or even empty space), but make sure you
       pass the same pointer to free_irq().

    */

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)
    err = request_irq(irq, handler, IRQF_SHARED, "Can", &can_minors[minor]);
#else
    err = request_irq(irq, handler, SA_SHIRQ, "Can", &can_minors[minor]);
#endif


    if( !err ){
      DBGPRINT(DBG_BRANCH,("Requested IRQ: %d @ 0x%lx",
      				irq, (unsigned long)handler));
      irq_requested[minor] = 1;
    }
    DBGOUT(); return err;
}


int can_freeirq(int minor, int irq )
{
    DBGIN();
    irq_requested[minor] = 0;
    /* pr_info(" Free IRQ %d  minor %d\n", irq, minor);  */
    free_irq(irq, &can_minors[minor]);
    DBGOUT();
    return 0;
}

/*
 * Perform Vendor-Init, that means sometimes CAN controller
 * or only board manufacturer specific initialization.
 *
 * Mainly it gets needed IO and IRQ resources and initializes
 * special hardware functions.
 *
 */

int can_vendor_init (int minor)
{
int i;

    DBGIN();
    can_range[minor] = CAN_RANGE;

    /* PCI scan for CC_CANPCI (or others ) has already scanned
     * for the io address.
     * Here we request the io areas for exclusive use with this driver
     */


    i = pci_request_region(can_pcidev[minor], 1, "can4linux-LED");
    if(i != 0) {
	pr_err("\tregion 1 can not be requested\n");
	DBGOUT(); return -EBUSY;
    }
    i = pci_request_region(can_pcidev[minor], 2, "can4linux-IO");
    if(i != 0) {
	pr_err("\tregion 2 can not be requested\n");
	DBGOUT(); return -EBUSY;
    }

    /* The Interrupt Line is already requested by th PC CARD Services
     * (in case of CPC-Card: cpc-card_cs.c)
    */

    /* pr_info("MAX_IRQNUMBER %d/IRQ %d\n", MAX_IRQNUMBER, IRQ[minor]); */
    if( IRQ[minor] > 0 && IRQ[minor] < MAX_IRQNUMBER ){
        if( can_requestirq( minor, IRQ[minor] , can_interrupt) ) {
	     pr_err("Can[%d]: Can't request IRQ %d \n", minor, IRQ[minor]);
	     DBGOUT(); return -EBUSY;
        }
    } else {
	/* Invalid IRQ number in /proc/.../IRQ */
	DBGOUT(); return -EINVAL;
    }
    DBGOUT(); return 0;
}


/* check memory region if there is a CAN controller
*  assume the controller was reseted before testing
*
*  The check for an available controller is difficult !
*  After an Hardware Reset (or power on) the Controller
*  is in the so-called 'BasicCAN' mode.
*     we can check for:
*        address  name      value
*	    0x00  mode       0x21
*           0x02  status     0xc0
*           0x03  interrupt  0xe0
* Once loaded the driver switches into 'PeliCAN' mode and things are getting
* difficult, because we now have only a 'soft reset' with not so  unique
* values. The values have to be masked before comparing.
*        address  name       mask   value
*	    0x00  mode
*           0x01  command    0xff    0x00
*           0x02  status     0x37    0x34
*           0x03  interrupt  0xfb    0x00
*
*/
int controller_available(void __iomem *a, int offset)
{
int minor = -1;
int address;

    DBGIN();
    pr_info("controller_available(%p, %d)\n", a, offset);
    address = (long int)a;

    if ( 0x21 == inb(address))  {
	/* compare reset values of status and interrupt register */
	if(   0x0c == inb(address + (2 * offset))
	   && 0xe0 == inb(address + (3 * offset)) ) {
	    return 1;
	} else {
	    return 0;
	}
    } else {
	/* may be called after a 'soft reset' in 'PeliCAN' mode */
	/*   value     address                     mask    */
	if(   0x00 ==  inb(address + (1 * offset))
	   && 0x34 == (inb(address + (2 * offset))    & 0x37)
	   && 0x00 == (inb(address + (3 * offset))    & 0xfb)
	  ) {
	    return 1;
	} else {
	    return 0;
	}
    }
    DBGOUT();
}

int pcimod_scan(void)
{
struct	pci_dev *pdev = NULL;
int	candev = 0;				/* number of found devices */
int i;


    while((pdev = pci_get_device (PCI_VENDOR_CAN_CC, PCI_ANY_ID, pdev))) {
	/* we are only interested in these types of boards */
	if( !
	    (pdev->device == PCI_DEVICE_CC_CANOPEN)
	    ||
	    (pdev->device == PCI_DEVICE_CC_CANDNET)
	    ) continue;

	pr_info("\tfound CC CANPCI: %s\n", pci_name(pdev));
	pr_info("\t                : %s at 0x%lx\n",
	    pcican_devnames[(pdev->device) - PCI_DEVICE_CC_MASK],
	    (long unsigned int)pci_resource_start(pdev, 1));

	if (pci_enable_device(pdev)) {
	    pr_err(" pci_enable_device not succeeded\n");
	    continue;
	}
	pr_info(" using IRQ %d\n", pdev->irq);

	/* get the io area to scan for a CAN controller available */
	i = pci_request_region(pdev, 2, "can4linux-IO");

	/* look for a CAN controller at address 0 of the selected area */
	if(controller_available(pci_iomap(pdev, 2, 128), 1)) {
	    pr_info("CAN: controller %d at pos 1\n", candev + 1);
	    can_pcidev[candev] = pdev; /* store pdev for release at close() */
	    if(candev > MAX_CHANNELS) {
		pr_err("CAN: only %d devices supported\n", MAX_CHANNELS);
		break; /* the devices scan loop */
	    }
	    proc_base[candev] = pci_resource_start(pdev, 2);
	    ledaddr[candev] = pci_resource_start(pdev, 1);
	    proc_iomodel[candev] = 'p';
	    IRQ[candev] = pdev->irq;
	    pr_info("ioports at 0x%x, IRQ %d\n",
		    (unsigned int)proc_base[candev], IRQ[candev]);
	    /* the value at the bit rate pre-scaler */
	    proc_clock = 8000000; /* it's on all boards the same */
	    candev++;
	} else {
	    pr_err(" CAN: NO CAN controller found at pos 1\n");
	    ;
	}
	/* in all cases, release for now the CAN io area */
	pr_info("release io 2\n");
	pci_release_region(pdev, 2);
    }
    if (candev == 0)
    	/* no can device found */
    	return -ENODEV;
    else
	return 0;
}


void board_clear_interrupts(int minor)
{
}

/*
The CC PCICAN Board has only one LED, it as a bi-color LED.
That said, only three states are possible
 LED off
 green on
 red on

*/

void can_control_led(int minor, command_par_t * argp)
{
int reg;
int val;

	DBGIN();
	/* 16 green, 32 red */
	if (argp->val1 == 1)
		val = 0x16;	/* green */
	else if (argp->val1 == 4)
		val = 0x32;	/* red */
	else {
		DBGOUT();
		return;
	}
	reg = inb(ledaddr[minor] + 0x54);
	if (argp->val2) {
		outb(val, ledaddr[minor] + 0x54);
	}
	else { /* Swich off */
		outb(0, ledaddr[minor] + 0x54);
	}
	DBGOUT();
}
